#include <arpa/inet.h>        // inet_pton() and inet_ntop()
#include <ctype.h>
#include <errno.h>
#include <net/if.h>           // struct ifreq
#include <netdb.h>            // struct addrinfo
#include <netinet/in.h>       // IPPROTO_RAW, IPPROTO_IP, IPPROTO_TCP, INET_ADDRSTRLEN
#include <netinet/ip.h>       // struct ip and IP_MAXPACKET (which is 65535)
#define __FAVOR_BSD           // Use BSD format of tcp header
#include <netinet/tcp.h>      // struct tcphdr
#include <pcap.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>           // strcpy, memset(), and memcpy()
#include <sys/ioctl.h>        // macro ioctl is defined
#include <sys/socket.h>       // needed for socket()
#include <sys/types.h>
#include <sys/types.h>        // needed for socket(), uint8_t, uint16_t, uint32_t
#include <unistd.h>           // close()
#include "build_packet.h"
#include "connect_to/connect_to.h"
#include "log.h"

#define SNAP_LEN 1600
#define SPECIAL_TTL 88
struct my_pcap_pkthdr {
    uint32_t caplen0; /* length of portion present */
    uint32_t caplen1; /* length of portion present */
    uint32_t caplen; /* length of portion present */
    uint32_t len;    /* length this packet (off wire) */
};

struct log_handle *log_handle = NULL;
int log_with_ts = 0;
int log_with_func = 0;
int log_with_file = 0;
int log_with_line = 0;
int iphead_offset = 16;
int repeat_count  = 1;
pcap_t *in_intf_pcap_handle = NULL;
#ifdef ENABLE_RAW_SOCKET
int pcap_output_fd = -1;
#else
pcap_t *outinf_pcap = NULL;
#endif
static unsigned long long packet_without_payload_count = 0;
static unsigned long long packet_with_payload_count = 0;
static inline void sigusr1_handler(int notuse)
{
    printf("wow, found zero payload packet %llu total\n", packet_without_payload_count);
    printf("wow, found have payload packet %llu total\n", packet_with_payload_count);
}

static inline void send_pack(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
    int index;
    struct iphdr *ip = (struct iphdr*)(packet + iphead_offset);

    if (ip->ttl != SPECIAL_TTL && ip->protocol == IPPROTO_TCP) {
        char *p = (char *)ip;
        struct tcphdr *tcphdr = (struct tcphdr *)(p + ip->ihl * 4);
        uint32_t tcphdr_len = tcphdr->th_off * 4;
        uint32_t payload_len = ntohs(ip->tot_len) - ip->ihl * 4 - tcphdr_len;
        if (payload_len <= 0) {
            packet_without_payload_count++;
            return;
        }
        packet_with_payload_count++;
        ip->ttl = SPECIAL_TTL;
        ip->check = 0;
        updata_ip_checksum((uint8_t *)packet, iphead_offset);
        update_tcp_pack_checksum((uint8_t *)packet, iphead_offset);
        for (index = 0; index < repeat_count; index++) {
#ifdef ENABLE_RAW_SOCKET
            struct sockaddr_in sin; memset(&sin, 0, sizeof(struct sockaddr_in));
            sin.sin_family = AF_INET;
            memcpy(&sin.sin_addr.s_addr, &ip->daddr, 4);
            if (sendto(pcap_output_fd, packet + iphead_offset, header->caplen - iphead_offset, 0, (struct sockaddr *) &sin, sizeof (struct sockaddr)) < 0) LOGDIE("sendto() failed [%m]");
#else
            int ret = pcap_sendpacket(outinf_pcap, packet, header->caplen); if (ret) LOGFEW("pcap_sendpacket error[%s]\n", pcap_geterr(outinf_pcap));
#endif
        }
    }
}

void usage()
{
    LLOG("Usage: [-i in_intf|-p listen_port] -o out_intf -r repeat_count -f filter_rule\n");
}

int main(int argc, char **argv)
{
	char *in_intf = "", *out_intf = "", *filter_rule = NULL, errbuf[PCAP_ERRBUF_SIZE];
	struct bpf_program bpf;
    int ret;
    unsigned short int listen_port = 0;

    int i;
    for (i = 1; i < argc; i++) {
        if (0) {}
        else if (strcmp(argv[i], "-i") == 0) { if (i + 1 >= argc) { LOGDIE("-i need arg!"); } in_intf      = argv[i+1]; i++; }
        else if (strcmp(argv[i], "-p") == 0) { if (i + 1 >= argc) { LOGDIE("-p need arg!"); } listen_port  = atoi(argv[i+1]); i++; }
        else if (strcmp(argv[i], "-o") == 0) { if (i + 1 >= argc) { LOGDIE("-o need arg!"); } out_intf     = argv[i+1]; i++; }
        else if (strcmp(argv[i], "-r") == 0) { if (i + 1 >= argc) { LOGDIE("-r need arg!"); } repeat_count = atoi(argv[i+1]); i++; }
        else if (strcmp(argv[i], "-f") == 0) { if (i + 1 >= argc) { LOGDIE("-f need arg!"); } filter_rule  = argv[i+1]; i++; }
        else { LOGERR("unknow arg %s\n", argv[i]); }
    }
    LLOG("in_intf:      %s\n", in_intf);
    LLOG("out_intf:     %s\n", out_intf);
    LLOG("listen_port:  %u\n", listen_port);
    LLOG("repeat_count: %d\n", repeat_count);
    LLOG("filter_rule:  %s\n", filter_rule);
    if (in_intf[0]   == 0 && listen_port == 0) { LOGERR("in_intf and listen_port unvalid\n");                         usage(); exit(1); }
    if (in_intf[0]        && listen_port     ) { LOGERR("in_intf and listen_port both valid, but only need one\n");   usage(); exit(1); }
    if (out_intf[0]  == 0)                     { LOGERR("out_intf unvalid\n");                                        usage(); exit(1); }
    if (repeat_count <= 0)                     { LOGERR("repeat_count %d <= 0\n", repeat_count);                      usage(); exit(1); }
    if (repeat_count >  5)                     { LOGERR("repeat_count %d > 5\n",  repeat_count);                      usage(); exit(1); }
    signal(SIGUSR1, sigusr1_handler);

    char cmd[512]; sprintf(cmd, "ethtool -K %s gso off gro off tso off lro off", in_intf); LLOG("exe: %s\n", cmd); system(cmd);
    //#define CMD_SET_ALL_INF_OFFLOAD "for i in `ifconfig | grep '^[a-z]' | awk '{print $1}' | grep -v '^lo' |  awk -F: '{print $1}'`; do echo ethtool -K $i tso off gso off gro off; ethtool -K $i tso off gso off gro off; done"
    //LLOG("exe: %s\n", CMD_SET_ALL_INF_OFFLOAD); system(CMD_SET_ALL_INF_OFFLOAD);

#ifdef ENABLE_RAW_SOCKET
    pcap_output_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (pcap_output_fd < 0) { LOGDIE("socket error[%m]\n"); exit(1); }
    struct ifreq ifr; memset(&ifr, 0, sizeof(ifr));
    snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", out_intf);
    //if (ioctl (pcap_output_fd, SIOCGIFINDEX, &ifr) < 0) { LOGDIE("ioctl() failed to find interface [%m]"); return (EXIT_FAILURE); }
    int on = 1;
    if (setsockopt (pcap_output_fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof (on)) < 0) LOGDIE("setsockopt() failed to set IP_HDRINCL [%m]"); // Set flag so socket expects us to provide IPv4 header.
    if (setsockopt (pcap_output_fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof (ifr)) < 0) LOGDIE("setsockopt() failed to bind to in_intf %s[%m]\n", out_intf);
#else
    outinf_pcap = pcap_open_live(out_intf, SNAP_LEN, 1, 1, errbuf); if(outinf_pcap == NULL) LOGDIE("pcap_open_live out_intf:[%s] err:[%s]\n", out_intf, errbuf);
#endif

    if (in_intf[0]) {
#ifdef ENABLE_RAW_SOCKET
        in_intf_pcap_handle = pcap_open_live(in_intf, SNAP_LEN, 1, 1, errbuf); if(in_intf_pcap_handle == NULL) LOGDIE("pcap_open_live in_intf:[%s] err:[%s]\n", in_intf, errbuf);
#else
        if (strcmp(in_intf, out_intf) == 0) in_intf_pcap_handle = outinf_pcap;
        else { in_intf_pcap_handle = pcap_open_live(in_intf, SNAP_LEN, 1, 1, errbuf); if(in_intf_pcap_handle == NULL) LOGDIE("pcap_open_live in_intf:[%s] err:[%s]\n", in_intf, errbuf); }
#endif

        int datalink_type = pcap_datalink(in_intf_pcap_handle);
        if (datalink_type == 113) {
            iphead_offset = 16; LLOG("data_link type 113 [linux cooked], set iphead_offset %d\n", iphead_offset);
        } else if (datalink_type == 1) {
            iphead_offset = 14; LLOG("data_link type 1 [IEEE 802.3], set iphead_offset %d\n", iphead_offset);
        } else {
            LOGDIE("data_link type %d [unknow]\n", datalink_type); exit(1);
        }

        if (pcap_compile(in_intf_pcap_handle, &bpf, filter_rule, 0, 0) == -1) { LOGDIE("filter rule err:[%s][%s]\n", filter_rule, pcap_geterr(in_intf_pcap_handle)); return -1; }
        if (pcap_setfilter(in_intf_pcap_handle, &bpf) == -1) { LOGDIE("set filter failed:[%s][%s]\n", filter_rule, pcap_geterr(in_intf_pcap_handle)); return -1; }

        pcap_activate(in_intf_pcap_handle);
        pcap_loop(in_intf_pcap_handle, -1, send_pack, NULL);
        pcap_freecode(&bpf);
        pcap_close(in_intf_pcap_handle);
    } else {
        int listen_fd = port_to_listen_fd(atoi(in_intf)); if (listen_fd < 0) LOGDIE("error\n");
        socklen_t len = 0;
        int fd = accept(listen_fd, NULL, &len); if (fd < 0) LOGDIE("dddddddddd\n");
        unsigned char data[1600];
        int data_len = 0;
        while (data_len < 24) {
            ret = read(fd, data, 24 - data_len); if (ret <= 0) LOGDIE("ddddddddddddd\n");
            data_len += ret;
        }
        data_len = 0;
        for (;;) {
            data_len = 0;
            while (data_len < sizeof(struct my_pcap_pkthdr)) {
                ret = read(fd, data + data_len, sizeof(struct my_pcap_pkthdr) - data_len); if (ret <= 0) LOGDIE("eeee");
                data_len += ret;
            }
            struct my_pcap_pkthdr my_pcap_pkghdr; memcpy(&my_pcap_pkghdr, data, sizeof(struct my_pcap_pkthdr)); if (my_pcap_pkghdr.caplen >= sizeof(data)) LOGDIE("dddddddddddddddd caplen %u\n", my_pcap_pkghdr.caplen);
            data_len = 0;
            while (data_len < my_pcap_pkghdr.caplen) {
                ret = read(fd, data + data_len, my_pcap_pkghdr.caplen - data_len); if (ret <= 0) LOGDIE("eeee %m");
                data_len += ret;
                if (data_len >= sizeof(data)) LOGDIE("eeee\n");
            }
            const struct pcap_pkthdr pcap_pkthdr = {.caplen = my_pcap_pkghdr.caplen };
            send_pack(NULL, &pcap_pkthdr, data);
        }
    }

	return 0;
}
